/*
 * proxy lab - A simple http proxy, using multi-threads and cache
 * Ge Gao       geg@andrew.cmu.edu
 * Chaomin Yu   chaominy@andrew.cmu.edu
 *
 * The proxy simply get the request and forward it to the server,
 * and then get the response back to the client.
 * 
 * When encounting errors such as header error, server connection error etc.,
 * we return an error page to the client and terminate the thread.
 * If we come across errors such as ECONNRESET and EPIPE when R&W,
 * we just terminate the thread without any messages.
 * In other situation, we simply terminates the process.
 *
 * The cache module is realized in cache.c and cache.h.
 * 
 * To avoid racing of gethostbyname, we modifies the openclientfd in csapp.c.
 * To handle the R&W errors, we modifies the Rio_read* & Rio_write* functions.
 *
 */

#include <stdio.h>
#include "csapp.h"
#include "cache.c"

#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define DEFAULT_PORT_NUM 80


static const char *user_agent = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
static const char *acceptstr = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
static const char *accept_encoding = "Accept-Encoding: gzip, deflate\r\n";
static const char *connection = "Connection: close\r\nProxy-Connection: close\r\n";

void *doit(void *vargp);
int parse_uri(char *uri, char *host, char *path, int *port);
int handle_requesthdrs(rio_t *rp, char *host, char *reqhdrs);
struct Object * receive_response(int fd, int clientfd);
int send_response(int clientfd, struct Object *object);
int forward_request(char *host, int port, char *path, char *reqhdrs);
void clienterror(int fd, char *cause, char *errnum, 
         char *shortmsg, char *longmsg);
int testRioread(int res);
int testRiowrite(int res);


int main(int argc, char **argv)
{
    int listenfd, *connfd, port, clientlen;
    struct sockaddr_in clientaddr;
    pthread_t tid;

    init_cache();
    Signal(SIGPIPE, SIG_IGN);
    /* Check command line args */
    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(1);
    }
    port = atoi(argv[1]);

    listenfd = Open_listenfd(port);
    while (1) {
        clientlen = sizeof(clientaddr);
        connfd = Malloc(sizeof(int));
        *connfd = Accept(listenfd, (SA *)&clientaddr, (socklen_t *)&clientlen); 
        Pthread_create(&tid, NULL, doit, connfd);
    }
    return 0;
}

/*
 * doit - handle one HTTP request/response transaction in a thread
 */
void *doit(void *vargp)
{
    int fd = *((int *)vargp);
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE], reqhdrs[MAXBUF];
    char host[MAXLINE], path[MAXLINE];
    int port;
    rio_t rio;
    int serverfd;
  
    Free(vargp);
    Pthread_detach(pthread_self());
    /* Read request line and headers */
    Rio_readinitb(&rio, fd);
    if (Rio_readlineb(&rio, buf, MAXLINE) < 0) {
        Close(fd);
        return NULL;
    }
    sscanf(buf, "%s %s %s", method, uri, version);      
    //method
    if (strcasecmp(method, "GET")) {       
        clienterror(fd, method, "501", "Not Implemented",
            "Proxy does not implement this method");
        Close(fd);
        return NULL;
    }       
    //version
    if (!strcasecmp(version, "HTTP/1.0") && !strcasecmp(version, "HTTP/1.1")) {
        clienterror(fd, version, "505", "HTTP Version not supported",
            "Proxy does not support this version");
        Close(fd);
        return NULL;
    }
    //uri
    if (!parse_uri(uri, host, path, &port)) {
        clienterror(fd, uri, "400", "URI format error",
            "URI format error");
        Close(fd);
        return NULL;
    }
    //request headers
    int ret = handle_requesthdrs(&rio, host, reqhdrs);
    if (ret == 0) {
        clienterror(fd, "request", "500", "Request headers too long",
            "Request headers too long");
        Close(fd);
        return NULL;
    } else if (ret == -1) {
        Close(fd);
        return NULL;
    }
    //search in the cache
    char *packed_uri = pack_uri(host, path, port); 
    struct Object * object;
    if((object = visit_cache(packed_uri)) != NULL)   
    {
        send_response(fd, object);

        Close(fd);
        return NULL;
    }
    //forward the request to the server
    serverfd = forward_request(host, port, path, reqhdrs);
    if (serverfd == -3) {
        Close(fd);
        Close(serverfd);
        return NULL;
    } else if (serverfd == -2) {
        clienterror(fd, "getaddrinfo failed", "404", "Not Found",
            "DNS failed");
        Close(fd);
        return NULL;
    } else if (serverfd == -1) {
        clienterror(fd, "socket", "403", "fobidden",
            "Connection failed");
        Close(fd);
        return NULL;
    }
    //send the response to the client and refesh the cache
    object = receive_response(serverfd, fd);
    if(object != NULL){
        update_cache(packed_uri, object);
    }

    Close(serverfd);
    Close(fd);
    return NULL;
}     


/* forward_request - forward request to the server
 *                   send the defaulted header and the inherit request headers
 * return value
 *    -3 write error
 *    -2 dns error
 *    -1 socket error
 *    else server fd
 *
 */
int forward_request(char *host, int port, char *path, char *reqhdrs)
{
    int fd = open_clientfd(host, port);
    char buf[MAXLINE];

    if (fd < 0)
        return fd;
    /* Print the HTTP request */
    sprintf(buf, "GET %s HTTP/1.0\r\n", path);
    if (Rio_writen(fd, buf, strlen(buf)) < 0)
        return -3;
    if (Rio_writen(fd, (void *)user_agent, strlen(user_agent)) < 0)
        return -3;
    if (Rio_writen(fd, (void *)acceptstr, strlen(acceptstr)) < 0)
        return -3;
    if (Rio_writen(fd, (void *)accept_encoding, strlen(accept_encoding)) < 0)
        return -3;
    if (Rio_writen(fd, (void *)connection, strlen(connection)) < 0)
        return -3;
    if (Rio_writen(fd, reqhdrs, strlen(reqhdrs)) < 0)
        return -3;
    if (Rio_writen(fd, "\r\n", 2) < 0)
        return -3;
    
    return fd;

}

/* receive_reponse - receive reponse from the server
 *                   store the reponse HTTP buf and body in the struct Object object
 *                   and then forward the respnose back to the client
 *                   record the content size
 * return value:
 * 1 - success
 * 0 - error
 */

struct Object *receive_response(int serverfd, int clientfd){
    rio_t rp;
    char buf[MAXLINE];
    char head[MAXBUF];
    char body[MAX_OBJECT_SIZE];
    int bodysize = -1;
    int len;

    memset(head, 0, sizeof(head));
    memset(body, 0, sizeof(body));
    //receive header
    Rio_readinitb(&rp, serverfd); 
    while(1){
        if (Rio_readlineb(&rp, buf, MAXLINE) < 0) {
            Close(serverfd);
            Close(clientfd);
            Pthread_exit("read fail");
        }
        sprintf(head, "%s%s", head, buf);
        if((strcmp(buf, "\r\n") == 0) || (strcmp(buf, "\n") == 0))
            break;
    }
    // write header
    if (Rio_writen(clientfd, head, strlen(head)) < 0) {
        Close(serverfd);
        Close(clientfd);
        Pthread_exit("write fail");
    }
    //receive body & write body
    bodysize = 0;
    while((len = Rio_readnb(&rp, body, MAX_OBJECT_SIZE)) > 0){
        if (Rio_writen(clientfd, body, len) < 0) {    
            Close(serverfd);
            Close(clientfd);
            Pthread_exit("write fail");
        }
        bodysize += len;
    }   
    if(bodysize > MAX_OBJECT_SIZE)
        return NULL;
    else {
        struct Object *new_object = Calloc(1, sizeof(struct Object));
        new_object->head = Calloc(strlen(head), 1);
        new_object->body = Calloc(bodysize, 1);
        new_object->bodysize = bodysize;
        memcpy(new_object->head,head,strlen(head));
        memcpy(new_object->body,body,bodysize);
        return new_object;
    }
}

/* send_response - send the object to client
 */
int send_response(int clientfd, struct Object *object){
    if (Rio_writen(clientfd, object->head, strlen(object->head)) < 0)
        return 0;
    if (Rio_writen(clientfd, object->body, object->bodysize) < 0)
        return 0;
    return 1;
}
     




/*
 * parse_uri - parse URI into host, port, and path
 *             return 0 if error, 1 if success
 * 
 */
int parse_uri(char *uri, char *host, char *path, int *port){
    char ptr[MAXLINE];

    /* if the uri doesn't begin with "http:", error*/
    if( (strncasecmp(uri, "http://", 7)) != 0)
    {
        return 0;
    }
    else{
        strcpy(ptr, uri+7);
    }

    
    if( strstr(ptr, "/") == NULL){
        /* no path exist */
        strcpy(path, "/");
        strcpy(host, ptr);
    }
    else{
        int hostlen = strcspn(ptr, "/");
        strcpy(path, ptr+hostlen);
        strncpy(host, ptr, hostlen);
        host[hostlen] = '\0';
    }
    
    char *c_port;
    if( (c_port = strstr(host, ":")) == NULL){
        *port = DEFAULT_PORT_NUM;
    }
    else{
        int i;
        /* port number contains non-digital */
        for(i = 1; i < strlen(c_port); i++){
            if(isdigit(c_port[i]) == 0)
                return 0;
        }
        *port = atoi(c_port+1);

        /* delete the port number part in string host */
        host[strlen(host) - strlen(c_port)] = '\0';
    }


    return 1;
}


/*
 * handle_requesthdrs - read and parse HTTP request headers
 * ignore User-Agent, Accept, Accept-Encoding, Connection, Proxy-Connection field
 * add Host field
 * inherit additional request headers
 * 
 * return value
 * -- -1 read error
 * -- 0  request too long
 * -- 1  success
 */
int handle_requesthdrs(rio_t *rp, char *host, char *reqhdrs)
{
    char buf[MAXLINE];
    char ignore[5][20] = {"User-Agent:", "Accept:", "Accept-Encoding:", "Connection:", "Proxy-Connection:"};
    int skim;
    int hostexist = 0;
    int len = 0;
    int buflen, i;

    while(1) {
        if (Rio_readlineb(rp, buf, MAXLINE-2) < 0)
            return -1;
        if (strncmp(buf, "\r\n", 2) == 0 || strncmp(buf, "\n", 1) == 0)
            break;
        buflen = strlen(buf);
        //skim the specified fields
        skim = 0;
        for (i = 0; i < 5; i++)
            if (!strncasecmp(buf, ignore[i], strlen(ignore[i]))) {
                skim = 1;
                break;
            }
        if (skim) continue;
        if (!strncasecmp(buf, "host:", 5))
            hostexist = 1;
        // adjust '\n' to "\r\n" 
        if (buf[buflen-2] != '\r') {
            buf[buflen-1] = '\r';
            buf[buflen] = '\n';
            buf[++buflen] = '\0';
        }

        len += buflen;
        if (len < MAXBUF) {
            strcpy(reqhdrs, buf);
            reqhdrs += buflen;
        }
        else 
            return 0;
    }
    if (!hostexist) {
        len += 7+strlen(host);
        if (len < MAXBUF) {
            strcpy(reqhdrs, "Host: ");
            reqhdrs += 6;
            strcpy(reqhdrs, host);
            strcpy(reqhdrs + strlen(host), "\r\n");
        }
        else 
            return 0;
    }
    return 1;
}

/*
 * clienterror - returns an error message to the client
 */
void clienterror(int fd, char *cause, char *errnum, 
         char *shortmsg, char *longmsg) 
{
    char buf[MAXLINE], body[MAXBUF];

    /* Build the HTTP response body */
    sprintf(body, "<html><title>Proxy Error</title>");
    sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
    sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
    sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
    sprintf(body, "%s<hr><em>Proxy</em>\r\n", body);
    /* Print the HTTP response */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);

    printf("%s", body);
    if (Rio_writen(fd, buf, strlen(buf)) < 0) {
        Close(fd);
        Pthread_exit("write error");
    }
    sprintf(buf, "Content-type: text/html\r\n");
    if (Rio_writen(fd, buf, strlen(buf)) < 0) {
        Close(fd);
        Pthread_exit("write error");
    }
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    if (Rio_writen(fd, buf, strlen(buf)) < 0) {
        Close(fd);
        Pthread_exit("write error");
    }
    if (Rio_writen(fd, body, strlen(body)) < 0) {
        Close(fd);
        Pthread_exit("write error");
    }
}
